//
//  SQLiteManager.swift
//  SQLite
//
//  Created by 武莎莎 on 15/9/17.
//  Copyright © 2015年 itcast. All rights reserved.
//

import Foundation

class SQLiteManager {
    
    static let sharedManager = SQLiteManager()
    /// 全局的数据库句柄，操纵整个数据库
    // Database handle
    private var db:COpaquePointer = nil
    private let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self)
    private let queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL)
    
    // SQLite 数据库不允许同时并发写入数据，如果要使用多线程，必须使用串行队列来执行操作
    func queueUpdate(action:(manager:SQLiteManager)->()) { // 使用队列进行预编译更新
        //
        dispatch_async(queue) { () -> Void in
            SQLiteManager.sharedManager.beginTransaction()
            // 要执行的操作
            action(manager: self)
            SQLiteManager.sharedManager.commitTransaction()
        }
    }
    
    
    // 预编译更新
    func prepareUpdate(sql:String, args: CVarArgType...) ->Bool {
        // 1、准备sql：预编译sql
        var stmt:COpaquePointer = nil
        if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
            print("SQL语句错误")
            return false
        }
        // 2.动态绑定 ？ 对应的数值
        var index:Int32 = 1 // 标识第一个？要从1开始
        // 遍历属性数组，绑定
        for value in args {
            if value is Int {
                sqlite3_bind_int64(stmt, index, sqlite3_int64(value as! Int))
            }else if value is Double {
                sqlite3_bind_double(stmt, index, value as! Double)
            }else if value is String {
                sqlite3_bind_text(stmt, index, value as! String, -1, SQLITE_TRANSIENT)
            }else if value is NSNull {
                sqlite3_bind_null(stmt, index)
            }
            index++
        }
        var result:Bool = true
        if sqlite3_step(stmt) != SQLITE_DONE {
            print("单步执行失败")
            result = false
        }
        // 复位：可以使sql可以被重复调用并执行
        if sqlite3_reset(stmt) != SQLITE_OK {
            print("复位失败")
            result = false
        }
        // 打印可知args是一个数组
//        print(args)
        // 释放
        sqlite3_finalize(stmt)
        
        print("SQL语句正确")
        return result
    }
    
    // 打开数据库
    // 将要打开的数据库存在的话，就会直接打开。如果不存在的话就会创建后打开
    func openDB(dbName:String) {
        // 要打开的数据库完整路径
        let dbPath = (NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last! as NSString).stringByAppendingPathComponent(dbName)
        print(dbPath)
    
        /**
        打开数据库
        :param: <Tfilename 数据库的完整路径
        :param: <TppDb     数据库句柄的地址
        */
        if sqlite3_open(dbPath, &db) != SQLITE_OK {
            print("打开数据库失败")
            return
        }
        print("打开数据库成功！")
        
        if createTable() {
            print("创表成功")
        }else {
            print("创表失败")
        }
        
    }
    /**
     执行insert，返回自增长的number（主键）
    
    :param: sql 插入语句
    
    :returns: 自增长的number
    */
    func execInsert(sql:String) ->Int {
        
        if execSQL(sql){
            // 自增长键值的id数值
            return Int(sqlite3_last_insert_rowid(db))
        }
        return -1
    }
    func beginTransaction() {
        execSQL("BEGIN TRANSACTION;")
    }
    
    func commitTransaction() {
        execSQL("COMMIT TRANSACTION;")
    }
    func rollbackTransaction() {
        execSQL("ROLLBACK TRANSACTION;")
    }

    func execSQL(sql:String) ->Bool {
        return (sqlite3_exec(db, sql, nil, nil, nil) == SQLITE_OK)
    }
    
    func createTable() ->Bool {
        let sql = "CREATE TABLE IF NOT EXISTS W_Student (\n" +
            "name text NOT NULL," +
            "score double," +
            "sex text," +
            "number integer PRIMARY KEY AUTOINCREMENT NOT NULL" +
        ") ;"
        return execSQL(sql)
    }
    
    // 返回查询到的所有的记录集
    func execRecordSet(sql:String) -> [[String : AnyObject]]? {
    
        // Statement handle： 语句句柄
        var stmt:COpaquePointer = nil
        /**
        *  1、准备SQL：预编译SQL
        *  @param COpaquePointer： Database handle 数据库句柄
        *  @param UnsafePointer<Int8>： 要执行的sql语句
        *  @param Int32##Int32#>: sql语句的字节长度，输入-1能够自动计算
        *  @param UnsafeMutablePointer<COpaquePointer>：Statement handle 语句的指针，后续的查询操作，全都依赖此指针，相当于编译好的sql，需要手动释放
        *  @param UnsafeMutablePointer<UnsafePointer<Int8>> 通常设置nil
        *  @return 查询记录的字典数组
        */
        if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
            print("SQL 语句错误")
            return nil
        }
        print("SQL 语句正确")
        
        var records = [[String : AnyObject]]()
        // 2.单步执行:每执行一步就会返回一条记录
        while sqlite3_step(stmt) == SQLITE_ROW {
        
            let dict = recordSet(stmt)
            records.append(dict)
        }
        // 释放语句句柄
        sqlite3_finalize(stmt)
        return records
    }
    private func recordSet(stmt:COpaquePointer) ->[String:AnyObject] {
            // 存储一条记录的数据
            var dict = [String : AnyObject]() 
            // 每一行有多少个字段
            let colCount = sqlite3_column_count(stmt)
            for col in 0..<colCount {
                // 每一个字段的名称
                let cName = sqlite3_column_name(stmt, col)
                let name = String(CString: cName, encoding: NSUTF8StringEncoding)
                
                // 每一个字段的数据类型
                let type = sqlite3_column_type(stmt, col)
                
                var value: AnyObject?
                // 根据数据类型获取数值
                switch type {
                case SQLITE_FLOAT:
                    value = sqlite3_column_double(stmt, col)
                case SQLITE_INTEGER:
                    value = Int(sqlite3_column_int64(stmt, col))
                case SQLITE3_TEXT:
                    let cValue = UnsafePointer<Int8>(sqlite3_column_text(stmt, col))
                    value = String(CString: cValue, encoding: NSUTF8StringEncoding)
                case SQLITE_NULL:
                    value = NSNull()
                default:
                    print("没有正确的数据类型")
                }
                dict[name!] = value ?? NSNull()
            }
            return dict
        }
    }
